package com.corewillsoft.fireflies.gamelaws;

import com.corewillsoft.fireflies.gameobjects.Firefly;
import com.corewillsoft.fireflies.gameobjects.GameObject;
import com.corewillsoft.fireflies.sounds.SoundsManager;
import com.corewillsoft.fireflies.util.MathUtils;

public class CollisionLaw extends BaseLaw {

	public CollisionLaw() {
	}

	@Override
	public LawType getType() {
		return LawType.COLLISION;
	}

	@Override
	public void applicate(GameObject gameObject) {

		if (gameObject instanceof Firefly) {
			processFirefly((Firefly) gameObject);
		}
	}

	private void processFirefly(Firefly firefly1) {

		// get current coordinates of fireflies top-left corners
		Firefly firefly2 = (Firefly) getParent();
		double x1 = firefly1.getX();
		double y1 = firefly1.getY();

		double x2 = firefly2.getX();
		double y2 = firefly2.getY();

		// get sizes of fireflies
		double size1 = firefly1.getArea().getHeight();
		double size2 = firefly2.getArea().getHeight();

		// get coordinates of centers of fireflies
		double xc1 = getFireflyCenterX(x1, size1);
		double yc1 = getFireflyCenterY(y1, size1);

		double xc2 = getFireflyCenterX(x2, size2);
		double yc2 = getFireflyCenterY(y2, size2);

		// get radii of circumcircles of fireflies
		double r1 = MathUtils.getCircumcircleRadius(size1);
		double r2 = MathUtils.getCircumcircleRadius(size2);

		/*
		 * further solution of collision problem is done for the 2D collision of two perfectly elastic
		 * bodies with circular shapes; the radii of these bodies are r1 and r2; coordinates of their
		 * centers are (xc1; yc1) and (xc2; yc2);
		 */

		// check if fireflies collides
		if (!isCollision(xc1, yc1, xc2, yc2, r1, r2)) {
			return;
		}

		SoundsManager.getInstance().playCollision();
		/*
		 * here we introduce new orthogonal 2D coordinate system (ksi, nu),
		 * where ksi-axis is drawn through centers of objects, and nu-axis is orthogonal to ksi-axis
		 */

		// determining phi - angle between x-axis and ksi-axis
		double phi = MathUtils.getLineAngle(xc1, yc1, xc2, yc2);

		// speed components of objects in (x, y) coordinate system BEFORE collision
		double v1xBefore = firefly1.getSpeedX();
		double v1yBefore = firefly1.getSpeedY();

		double v2xBefore = firefly2.getSpeedX();
		double v2yBefore = firefly2.getSpeedY();

		/*
		 * calculation of speed components of objects in (ksi, nu) coordinate system BEFORE collision
		 * (using formulae of transformation of coordinates (x, y) -> (ksi, nu)
		 */
		double v1ksiBefore = MathUtils.getKsiByXY(v1xBefore, v1yBefore, phi);
		double v1nuBefore = MathUtils.getNuByXY(v1xBefore, v1yBefore, phi);

		double v2ksiBefore = MathUtils.getKsiByXY(v2xBefore, v2yBefore, phi);
		double v2nuBefore = MathUtils.getNuByXY(v2xBefore, v2yBefore, phi);

		/*
		 * calculation of speed components of objects in (ksi, nu) coordinate system AFTER collision
		 * (these formulae are derived from momentum conservation law (total momentum of two objects
		 * before and after collision is constant) and energy conservation law (total kinetic energy of
		 * two objects before and after collision is constant))
		 */
		double v1ksiAfter = v2ksiBefore;
		double v1nuAfter = v1nuBefore;

		double v2ksiAfter = v1ksiBefore;
		double v2nuAfter = v2nuBefore;

		/*
		 * calculation of speed components of objects in (x, y) coordinate system AFTER collision
		 * (using formulae of transformation of coordinates (ksi, nu) -> (x, y)
		 */
		double v1xAfter = MathUtils.getXByKsiNu(v1ksiAfter, v1nuAfter, phi);
		double v1yAfter = MathUtils.getYByKsiNu(v1ksiAfter, v1nuAfter, phi);

		double v2xAfter = MathUtils.getXByKsiNu(v2ksiAfter, v2nuAfter, phi);
		double v2yAfter = MathUtils.getYByKsiNu(v2ksiAfter, v2nuAfter, phi);

		/*
		 * move fireflies to such positions that distance between them will be equal to sum of their circumcircles radii
		 * (so they will not be stuck in each other)
		 */
		correctFirefliesPositions(firefly1, firefly2);

		// set new speeds of fireflies (they are in (x, y) coordinates)
		firefly1.setSpeed((float) v1xAfter, (float) v1yAfter);
		firefly2.setSpeed((float) v2xAfter, (float) v2yAfter);
	}

	/**
	 * Corrects positions of fireflies in such way that the distance between them equals to the sum of radii of their
	 * circumcircles;
	 * both fireflies are moved equally from each other along line connecting their centers
	 * @param firefly1
	 *            - first firefly
	 * @param firefly2
	 *            - second firefly
	 */
	private void correctFirefliesPositions(Firefly firefly1, Firefly firefly2) {

		// get sizes of fireflies
		double size1 = firefly1.getArea().getHeight();
		double size2 = firefly2.getArea().getHeight();

		// get radii of circumcircles of fireflies
		double r1 = MathUtils.getCircumcircleRadius(size1);
		double r2 = MathUtils.getCircumcircleRadius(size2);

		// get coordinates of centers of fireflies
		double xc1 = getFireflyCenterX(firefly1.getX(), size1);
		double yc1 = getFireflyCenterY(firefly1.getY(), size1);
		double xc2 = getFireflyCenterX(firefly2.getX(), size2);
		double yc2 = getFireflyCenterY(firefly2.getY(), size2);

		// determine angle between x-axis and ksi-axis
		double phi = MathUtils.getLineAngle(xc1, yc1, xc2, yc2);

		/*
		 * determine coordinates of fireflies in (ksi, nu) orthogonal coordinate system with the center in the center of
		 * first firefly
		 * and with angle phi between x-axis and y-axis
		 */
		double ksi1 = 0;
		double nu1 = 0;

		double ksi2 = MathUtils.getKsiByXY(xc2 - xc1, yc2 - yc1, phi);
		double nu2 = MathUtils.getNuByXY(xc2 - xc1, yc2 - yc1, phi);

		// current distance between fireflies' centers
		double currentDistance = Math.abs(ksi2 - ksi1);

		// required distance which should be added to existing distance between fireflies, so total distance between
		// will be equal r1 + r2
		double requiredDeltaKsi = (r1 + r2) - currentDistance;

		/*
		 * add half of this required distance to ksi coordinate of each firefly (keeping nu coordinate!);
		 * take into account possible relative position of two fireflies
		 */
		if (ksi2 > ksi1) {
			ksi1 -= requiredDeltaKsi / 2;
			ksi2 += requiredDeltaKsi / 2;
		} else {
			ksi1 += requiredDeltaKsi / 2;
			ksi2 -= requiredDeltaKsi / 2;
		}

		// calculate correct coordinates of fireflies centers in (x, y) system
		double xc1Corrected = MathUtils.getXByKsiNu(ksi1, nu1, phi) + xc1;
		double yc1Corrected = MathUtils.getYByKsiNu(ksi1, nu1, phi) + yc1;

		double xc2Corrected = MathUtils.getXByKsiNu(ksi2, nu2, phi) + xc1;
		double yc2Corrected = MathUtils.getYByKsiNu(ksi2, nu2, phi) + yc1;

		// calculate correct coordinates of left-top corners of fireflies
		double x1Corrected = xc1Corrected - size1 / 2;
		double y1Corrected = yc1Corrected - size1 / 2;

		double x2Corrected = xc2Corrected - size2 / 2;
		double y2Corrected = yc2Corrected - size2 / 2;

		firefly1.setPosition((float) x1Corrected, (float) y1Corrected);
		firefly2.setPosition((float) x2Corrected, (float) y2Corrected);
	}

	/**
	 * Returns X coordinate of firefly center by x coordinate of firefly's top-left corner and its size
	 * @param topLeftX
	 *            - X coordinate of top-left corner of the firefly
	 * @param size
	 *            - size (width or height) of firefly (consider firefly as square, so size = width = height)
	 * @return X coordinate of firefly center
	 */
	private double getFireflyCenterX(double topLeftX, double size) {
		return (topLeftX + MathUtils.getCircumcircleRadius(size));
	}

	/**
	 * Returns Y coordinate of firefly center by x coordinate of firefly's top-left corner and its size
	 * @param topLeftY
	 *            - Y coordinate of top-left corner of the firefly
	 * @param size
	 *            - size (width or height) of firefly (consider firefly as square, so size = width = height)
	 * @return Y coordinate of firefly center
	 */
	private double getFireflyCenterY(double topLeftY, double size) {
		return (topLeftY + MathUtils.getCircumcircleRadius(size));
	}

	/**
	 * Checks if two circular 2D shapes collided
	 * @param xc1
	 *            - X coordinate of the center of first body
	 * @param yc1
	 *            - Y coordinate of the center of first body
	 * @param xc2
	 *            - X coordinate of the center of second body
	 * @param yc2
	 *            - Y coordinate of the center of second body
	 * @param r1
	 *            - radius of first body
	 * @param r2
	 *            - radius of second body
	 * @return true, if bodies collided
	 */
	private boolean isCollision(double xc1, double yc1, double xc2, double yc2, double r1, double r2) {
		double distance = MathUtils.getDistanceBetweenPoints(xc1, yc1, xc2, yc2); // distance between fireflies centers
		return (distance <= (r1 + r2));
	}
}
